﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ServiceProcess;
using System.Text;
using Microsoft.ApplicationServer.Caching;
using System.Threading;
using System.Xml;
using CacheHandlerManager;

namespace ASNetWin
{
    public partial class ASNetWinSVC : ServiceBase
    {
        private Thread healthUpdate;
        
        //config vars
        private XmlDocument xConf;
        private string hostName;
        private int cachePort;
        private string cacheHealthName;
        private string URL;
        private string cacheSessionsName;
        private int lockTimeout;
        private int counterWait;

        public ASNetWinSVC()
        {
            InitializeComponent();

            xConf = new XmlDocument();
            //hardcoded path - config must be in C:\ASNet\
            xConf.Load("C:\\ASNet\\local_conf.xml");
            hostName = xConf.GetElementsByTagName("localHostName").Item(0).InnerText;
            cachePort = Int32.Parse(xConf.GetElementsByTagName("localHostCachePort").Item(0).InnerText);
            cacheHealthName = xConf.GetElementsByTagName("healthPartition").Item(0).InnerText;
            URL = xConf.GetElementsByTagName("URL").Item(0).InnerText;
            cacheSessionsName = xConf.GetElementsByTagName("sessionPartition").Item(0).InnerText;
            lockTimeout = Int32.Parse(xConf.GetElementsByTagName("lockTimeout").Item(0).InnerText);
            counterWait = Int32.Parse(xConf.GetElementsByTagName("counterWait").Item(0).InnerText);

            //worker thread
            ServerHealthMonitorCacheUpdate shmcu = new ServerHealthMonitorCacheUpdate(this);
            healthUpdate = new Thread(new ThreadStart(shmcu.doWork));
        }

        protected override void OnStart(string[] args)
        {
            //on start kick off the worker thread execution
            healthUpdate.Start();
        }

        protected override void OnStop()
        {
            //on stop - abort the thread, which also results in some cleaning up
            healthUpdate.Abort();
            base.OnStop();
        }

        //these are used later on to access the config file settings
        public string HostName { get { return hostName; } }
        public int CachePort { get { return cachePort; } }
        public string CacheHealthName { get { return cacheHealthName; } }
        public string PumpURL { get { return URL; } }
        public string CacheSessionsName { get { return cacheSessionsName; } }
        public int LockTimeout { get { return lockTimeout; } }
        public int CounterWaitTime { get { return counterWait; } }
    }

    //the thread containing bit where the work gets done
    class ServerHealthMonitorCacheUpdate
    {
        private ASNetWinSVC asns;
        private CacheHandler chHealth;
        private CacheHandler chSessions;
        public ServerHealthMonitorCacheUpdate(ASNetWinSVC asns)
        {
            this.asns = asns;
        }
        
        //the main bit
        public void doWork()
        {
            //init the two caches
            chHealth = new CacheHandler(asns.HostName, asns.CachePort, asns.CacheHealthName);
            chSessions = new CacheHandler(asns.HostName, asns.CachePort, asns.CacheSessionsName);

            ServerHealthMonitor shm = new ServerHealthMonitor();
            LinkedList<KeyValuePair<string, string>> freeList = null;
            LinkedList<KeyValuePair<string, string>> busyList = null;

            //now this is not great, and should probably be fixed - string concat to show the timestamp on the refresh event.
            //it is necessary so we know when to time out the list if it doesn't get refreshed
            KeyValuePair<string, string> kvp;

            int sh;

            try
            {
                while (true)
                {
                    //refreshing the timestamp
                    kvp = new KeyValuePair<string, string>(asns.HostName + "|" + DateTime.Now.ToString(String.Format("yyyy/MM/dd HH:mm:ss")), asns.PumpURL);

                    //check the server state (0: down; 1: busy; 2: free)
                    sh = shm.GetServerLoadKPI(asns.HostName, asns.CounterWaitTime);

                    //note how we don't hit the cache directly but always go though our CacheHandler
                    freeList = (LinkedList<KeyValuePair<string, string>>)chHealth.getWithLock("freeList", asns.LockTimeout);
                    busyList = (LinkedList<KeyValuePair<string, string>>)chHealth.getWithLock("busyList", asns.LockTimeout);

                    //here we check if we actually have a free and busy lists. if we don't we create them
                    if (freeList == null)
                    {
                        freeList = new LinkedList<KeyValuePair<string, string>>();
                    }

                    if (busyList == null)
                    {
                        busyList = new LinkedList<KeyValuePair<string, string>>();
                    }

                    //here we check the state of the server and update the freeList accordingly
                    LinkedList<KeyValuePair<string, string>> i = new LinkedList<KeyValuePair<string, string>>(freeList);
                    KeyValuePair<string, string> f = new KeyValuePair<string, string>();
                    foreach (KeyValuePair<string, string> kvpCheck in i)
                    {
                        string key = kvpCheck.Key;

                        if (DateTime.ParseExact(key.Substring(key.IndexOf("|") + 1), "yyyy/MM/dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture) < 
                                DateTime.Now.AddMinutes(-1))
                        {
                            freeList.Remove(kvpCheck);
                            removeSessionsForHost((string)kvpCheck.Value);
                        }
                        else
                        {
                            if (key.Substring(0, key.IndexOf("|")) == asns.HostName)
                            {
                                if (sh == 0 || sh == 1)
                                {
                                    freeList.Remove(kvpCheck);
                                }
                                f = new KeyValuePair<string, string>(kvpCheck.Key, kvpCheck.Value);
                            }
                        }
                    }

                    if (f.Key != null && sh == 2)
                    {
                        freeList.AddAfter(freeList.Find(f), kvp);
                        freeList.Remove(f);
                    }
                    else if (sh == 2)
                    {
                        freeList.AddLast(kvp);
                    }

                    //now almost the same as above for the busyList
                    i = new LinkedList<KeyValuePair<string, string>>(busyList);
                    f = new KeyValuePair<string, string>();
                    foreach (KeyValuePair<string, string> kvpCheck in i)
                    {
                        string key = kvpCheck.Key;
                        if (DateTime.ParseExact(key.Substring(key.IndexOf("|") + 1), "yyyy/MM/dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture) < 
                                DateTime.Now.AddMinutes(-1))
                        {
                            busyList.Remove(kvpCheck);
                            removeSessionsForHost(kvpCheck.Value);
                        }
                        else
                        {
                            if (key.Substring(0, key.IndexOf("|")) == asns.HostName)
                            {
                                if (sh == 0 || sh == 2)
                                {
                                    busyList.Remove(kvpCheck);
                                }
                                f = new KeyValuePair<string, string>(kvpCheck.Key, kvpCheck.Value);
                            }
                        }
                    }

                    if (f.Key != null && sh == 1)
                    {
                        busyList.AddAfter(busyList.Find(f), kvp);
                        busyList.Remove(f);
                    }
                    else if (sh == 1)
                    {
                        busyList.AddLast(kvp);
                    }

                    //make sure we remove the sessions for servers which are down
                    if (sh == 0)
                    {
                        removeSessionsForHost(asns.PumpURL);
                    }

                    //commit and release locks
                    chHealth.putWithUnlock("freeList", freeList);
                    chHealth.putWithUnlock("busyList", busyList);
                }
            }
            catch (ThreadAbortException)
            {
                //here we clean up if we get interrupted by an abort (on service stop)
                chHealth.unlock("freeList");
                chHealth.unlock("busyList");

                removeHostFromList("all", asns.HostName);
                removeSessionsForHost(asns.PumpURL);
            }
        }

        //getting rid of all sessions for a host
        private void removeSessionsForHost(string pumpUrl)
        {
            List<KeyValuePair<string, object>> allSessions = new List<KeyValuePair<string, object>>(chSessions.getAllObjectsInRegion(asns.CacheSessionsName));

            foreach (KeyValuePair<string, object> skvp in allSessions)
            {
                if ((string)skvp.Value == pumpUrl)
                {
                    chSessions.removeFromCache(skvp.Key);
                }
            }
        }

        //getting rid of a host in our lists - params are either freeList, busyList or all. all cleans up both
        private void removeHostFromList(string list, string hostName)
        {
            LinkedList<KeyValuePair<string, string>> l = null;
            LinkedList<KeyValuePair<string, string>> m = null;
            string v;
            if (list == "freeList" || list == "busyList")
            {
                l = (LinkedList<KeyValuePair<string, string>>)chHealth.getWithLock(list, asns.LockTimeout);
                if (l == null)
                {
                    l = new LinkedList<KeyValuePair<string, string>>();
                }
                m = new LinkedList<KeyValuePair<string, string>>(l);
                foreach (KeyValuePair<string, string> kvp in m)
                {
                    v = kvp.Key;
                    if (v.Substring(0, v.IndexOf("|")) == hostName)
                    {
                        l.Remove(kvp);
                        break;
                    }
                }

                chHealth.putWithUnlock(list, l);
            }
            else if (list == "all")
            {
                //call ourselves twice
                removeHostFromList("freeList", hostName);
                removeHostFromList("busyList", hostName);
            }
        }
    }
}